package com.jvxb.manage.livable.service.dataAlert.impl;

import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.Condition;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.jvxb.common.constants.DbConstants;
import com.jvxb.common.utils.BeanUtil;
import com.jvxb.common.utils.CommonUtil;
import com.jvxb.manage.livable.entity.*;
import com.jvxb.manage.livable.service.*;
import com.jvxb.manage.livable.service.dataAlert.MySQLDataAlertService;
import com.jvxb.manage.livable.util.JdbcUtil;
import com.jvxb.manage.livable.util.SqlCheckUtil;
import com.jvxb.manage.livable.util.WorkOrderUtil;
import com.jvxb.manage.livable.vo.DataAlterVo;
import com.jvxb.manage.livable.vo.WorkOrderOperateVo;
import com.jvxb.modules.configuration.exception.ServiceException;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author jvxb
 * @since 2022-02-09
 */
@Log4j2
@Service(DbConstants.MYSQL + DbConstants.DATA_ALERT_SERVICE)
public class MySQLDataAlertServiceImpl implements MySQLDataAlertService {

    @Autowired
    SysDictService sysDictService;
    @Autowired
    MysqlChangeService mysqlChangeService;
    @Autowired
    MysqlChangeDataService mysqlChangeDataService;
    @Autowired
    WorkOrderService workOrderService;
    @Autowired
    AssetsInstanceService assetsInstanceService;

    @Override
    public String checkMysql(Long clusterId, String command) {
        //解析变更命令
        List<SQLStatement> sqlStatements = SqlCheckUtil.parseStatements(command);
        Integer maxCommandCount = Integer.valueOf(sysDictService.getUniValueByGroupAndKey("data_alter_mysql", "maxCommandCount", "1000000"));
        if (sqlStatements.size() > maxCommandCount) {
            throw new RuntimeException("单个工单最多支持" + maxCommandCount + "条变更命令");
        }
        //检测变更类型
        SqlCheckUtil.checkSqlType(sqlStatements);
        //检测crate语句
        SqlCheckUtil.checkCreateGrammar(sqlStatements);
        //检测操作库
        SqlCheckUtil.checkAlterDb(sqlStatements);
        //检测alter语句
        SqlCheckUtil.checkAlterGrammar(sqlStatements);
        return null;
    }

    @Override
    @Transactional
    public void createOrder(DataAlterVo dataAlterVo) {
        try {
            //1）记录变更信息
            MysqlChange mysqlChange = getMySQLChangeInfo(dataAlterVo);
            mysqlChangeService.save(mysqlChange);
            //2）拆分变更命令
            List<MysqlChangeData> mysqlChangeDataList = getMysqlChangeDataList(mysqlChange);
            //3）新增变更工单
            WorkOrder workOrder = WorkOrderUtil.createDataAlertOrder(dataAlterVo);
            workOrderService.save(workOrder);
            //关联工单
            mysqlChange.setWorkOrderId(workOrder.getId());
            mysqlChangeService.updateById(mysqlChange);
            if (CommonUtil.notNullOrEmpty(mysqlChangeDataList)) {
                mysqlChangeDataList.stream().forEach(e -> e.setWorkOrderId(workOrder.getId()));
                mysqlChangeDataService.saveBatch(mysqlChangeDataList);
            }
        } catch (Exception e) {
            log.error(e);
            throw new RuntimeException("创建变更工单异常：" + e.getMessage());
        }
    }

    private MysqlChange getMySQLChangeInfo(DataAlterVo dataAlterVo) {
        MysqlChange mysqlChange = new MysqlChange();
        BeanUtil.copy(dataAlterVo, mysqlChange);
        mysqlChange.setCreaterId(dataAlterVo.getCreator().getUserCode());
        mysqlChange.setCreator(JSON.toJSONString(dataAlterVo.getCreator()));
        mysqlChange.setIsEnd(MysqlChange.IS_END_NO);
        mysqlChange.setCreateTime(new Date());
        return mysqlChange;
    }

    private List<MysqlChangeData> getMysqlChangeDataList(MysqlChange mysqlChange) {
        //如果是回滚工单不拆解命令
        if (mysqlChange.getIsRollback().equals(MysqlChange.IS_ROLLBACK_YES)) {
            return null;
        }
        String errMsg = null;
        List<MysqlChangeData> mysqlChangeDataList = new ArrayList<>();
        try {
            int num = 1;
            List<SQLStatement> sqlStatements = SqlCheckUtil.parseStatements(mysqlChange.getCommand());
            for (SQLStatement statement : sqlStatements) {
                MysqlChangeData mysqlChangeData = new MysqlChangeData();
                mysqlChangeData.setCommand(statement.toString());
                mysqlChangeData.setIsRollback(mysqlChange.getIsRollback());
                mysqlChangeData.setNum(num++);
                mysqlChangeDataList.add(mysqlChangeData);
            }
        } catch (Exception e) {
            errMsg = e.getMessage();
            log.error("拆解mysql命令异常，请联系平台值班：" + errMsg, e);
        }
        if (errMsg == null && CommonUtil.notNullOrEmpty(mysqlChangeDataList)) {
            return mysqlChangeDataList;
        } else {
            throw new RuntimeException("拆解mysql命令异常，请联系平台值班：" + errMsg);
        }
    }

    @Override
    @Async("asyncAlterExecutor")
    public void executeOrder(WorkOrderOperateVo operateVo) {
        MysqlChange mysqlChange = mysqlChangeService.getOne(Condition.<MysqlChange>create()
                .eq(MysqlChange.WORK_ORDER_ID, operateVo.getId()).select(MysqlChange.ID, MysqlChange.CLUSTER_ID, MysqlChange.IS_ROLLBACK));
        if (MysqlChange.IS_ROLLBACK_YES.equals(mysqlChange.getIsRollback())) {
            throw new ServiceException("暂不支持回滚工单");
        }

        AssetsInstance mysqlInstance = assetsInstanceService.getOne(Condition.<AssetsInstance>create().eq(AssetsInstance.CLUSTER_ID, mysqlChange.getClusterId()).last("limit 1"));
        if (mysqlInstance == null) {
            throw new RuntimeException("没有可用实例！集群id=" + mysqlChange.getClusterId());
        }

        //查询未执行、执行失败的命令
        List<MysqlChangeData> changeDataList = mysqlChangeDataService.list(Condition.<MysqlChangeData>create()
                .eq(MysqlChangeData.WORK_ORDER_ID, operateVo.getId()).last(" and (is_success is null or is_success = 0)"));
        //开始执行时间
        long start = System.currentTimeMillis();
        //执行命令
        String errMsg = execute(changeDataList, mysqlInstance);
        //执行成功
        if (errMsg == null) {
            UpdateWrapper updateWrapper = new UpdateWrapper();
            updateWrapper.eq(MysqlChange.ID, mysqlChange.getId());
            updateWrapper.set(MysqlChange.IS_END, MysqlChange.IS_END_YES);
            updateWrapper.set(MysqlChange.RESPONSE_TIME, System.currentTimeMillis() - start);
            mysqlChangeService.update(mysqlChange, updateWrapper);
            workOrderService.executeSuccess(operateVo);
        }
    }

    private String execute(List<MysqlChangeData> changeDataList, AssetsInstance mysqlInstance) {
        String errMsg = null;
        Connection connection = null;
        try {
            connection = JdbcUtil.getConnection(mysqlInstance.getHost(), mysqlInstance.getPort());
            for (MysqlChangeData mysqlChangeData : changeDataList) {
                try {
                    JdbcUtil.execute(connection, mysqlChangeData.getCommand());
                    mysqlChangeData.setIsSuccess(MysqlChangeData.IS_SUCCESS_YES);
                    mysqlChangeDataService.updateById(mysqlChangeData);
                } catch (Exception e) {
                    errMsg = String.format("执行变更工单[%s]异常，位置：%s，异常：%s", mysqlChangeData.getWorkOrderId(), mysqlChangeData.getNum(), e.getMessage());
                    log.error(errMsg, e);
                    executeFail(mysqlChangeData, errMsg);
                    break;
                }
            }
        } catch (Exception e) {
            errMsg = e.getMessage();
            workOrderService.executeFail(changeDataList.get(0).getWorkOrderId(), errMsg);
        } finally {
            JdbcUtil.close(connection);
        }
        return errMsg;
    }

    public void executeFail(MysqlChangeData mysqlChangeData, String errMsg) {
        //命令执行失败
        mysqlChangeData.setIsSuccess(MysqlChangeData.IS_SUCCESS_NO);
        mysqlChangeData.setFailDesc(errMsg);
        mysqlChangeDataService.updateById(mysqlChangeData);
        //工单执行异常
        workOrderService.executeFail(mysqlChangeData.getWorkOrderId(), errMsg);
    }
}
